package mysgoog.gtp;

import java.io.IOException;
import java.util.*;
import mysgoog.bot.*;
import mysgoog.util.*;

public abstract class GTP
{
    protected final HashSet<String> req_cmds = new HashSet<String>();
    {
        String[] cmds = { "protocal_version", "name", "version", "known_command", "list_commands",
                "quit", "boardsize", "clear_board", "komi", "play", "genmove", "showboard" };
        for (String cmd : cmds)
            req_cmds.add(cmd);
    }

    protected String protocol_version()
    {
        return "2";
    }
    protected String name()
    {
        return "MysGoogBot";
    }
    protected String version()
    {
        return "0.1";
    }
    protected String known_command(String command)
    {
        if (req_cmds.contains(command))
            return "true";
        else
            return "false";
    }
    protected Collection<String> list_commands()
    {
        return req_cmds;
    }
    /**
     * The session is terminated and the connection is closed.<br>
     * The full response of this command must be sent before the engine closes
     * the connection. The controller must receive the response before the
     * connection is closed on its side.
     */
    abstract protected void quit();
    protected void boardsize(int size)
    {
        Config.setBoardSize(size);
    }
    abstract void clear_board();
    protected void komi(float value)
    {
        Config.komi = value;
    }
    /**
     * Handicap stones are placed on the board according to the specication in
     * section 4.1.1. <br>
     * This command is only valid if the board is empty. See section 4.1.1 for
     * valid number of handicap stones. The handicap stones are not included in
     * the move history.
     * 
     * @param value
     *            Number of handicap stones.
     * @return A list of the vertices where handicap stones have been placed.
     * @fails Syntax error. invalid number of stones, board not empty
     */
    abstract protected ArrayList<Vertex> fixed_handicap(int value);
    /**
     * Handicap stones are placed on the board on the vertices the engine
     * prefers. See also section 4.1.2. <br>
     * This command is only valid if the board is empty. The engine may place
     * fewer than the requested number of stones on the board under certain
     * circumstances, as discussed in section 4.1.2. The controller can check
     * this by counting the number of vertices in the response. The handicap
     * stones are not included in the move history. Vertices must not be
     * repeated or include \pass".
     * 
     * @param value
     *            Number of handicap stones.
     * @fails Syntax error, invalid number of stones, board not empty,bad vertex
     *        list
     */
    abstract protected ArrayList<Vertex> place_free_handicap(int value);
    /**
     * Handicap stones are placed on the vertices as requested. This command is
     * only valid if the board is empty. The list must have at least two
     * elements and no more than the number of board vertices minus one. The
     * engine must accept the handicap placement. The handicap stones are not
     * included in the move history. Vertices must not be repeated or include
     * \pass".
     * 
     * @param vertices
     *            A list of vertices where handicap stones should be placed on
     *            the board.
     * @fails syntax error, board not empty, bad vertex list
     */
    abstract void set_free_handicap(ArrayList<Vertex> vertices);
    /*
     * 6.3.3 Core Play Commands
     */
    /**
     * A stone of the requested color is played at the requested vertex. The
     * number of captured stones is updated if needed and the move is added to
     * the move history. <br>
     * Consecutive moves of the same color are not considered illegal from the
     * protocol point of view.
     * 
     * @param color
     *            Color of the move
     * @param vertex
     *            Vertex of the move
     * @fails syntax error, illegal move. In the latter case, fails with the
     *        error message \illegal move".
     */
    abstract void play(Color color, Vertex vertex);
    /**
     * A stone of the requested color is played where the engine chooses. The
     * number of captured stones is updated if needed and the move is added to
     * the move history. <br>
     * Notice that \pass" is a valid vertex and should be returned if the engine
     * wants to pass. Use \resign" if you want to give up the game. The
     * controller is allowed to use this command for either color, regardless
     * who played the last move.
     * 
     * @param color
     *            Color for which to generate a move.
     * @return Vertex where the move was played or the string \resign".
     * @throws ClassNotFoundException 
     * @throws IOException 
     */
    abstract Vertex genmove(Color color) throws IOException, ClassNotFoundException;
    /**
     * The board conguration and the number of captured stones are reset to the
     * state before the last move. The last move is removed from the move
     * history. <br>
     * If you want to take back multiple moves, use this command multiple times.
     * The engine may fail to undo if the move history is empty or if the engine
     * only maintains a partial move history, which has been exhausted by
     * previous undos. It is never possible to undo handicap placements. Use
     * clear board if you want to start over. An engine which never is able to
     * undo should not include this command among its known commands.
     * 
     * @fails If the engine is unable to take back the last move, fails with the
     *        error message "cannot undo".
     */
    abstract void undo();

    abstract String showboard();

    /**
     * This is the main loop parser for the GTP. This will parse the inputs and
     * call the appropriate method to complete that command.
     * 
     * @param command
     *            The command to be parsed.
     * @throws ClassNotFoundException 
     * @throws IOException 
     */
    public void parse(String command) throws IOException, ClassNotFoundException
    {
        Lib.debug(command);
        StringTokenizer parser = new StringTokenizer(command);
        String cmnd = parser.nextToken();
        if (cmnd.compareTo("protocol_version") == 0)
        {
            System.out.println("= " + protocol_version());
        } else if (cmnd.compareTo("name") == 0)
        {
            System.out.println("= " + name());
        } else if (cmnd.compareTo("version") == 0)
        {
            System.out.println("= " + version());
        } else if (cmnd.compareTo("known_command") == 0)
        {
            System.out.println("= " + known_command(parser.nextToken()));
        } else if (cmnd.compareTo("list_commands") == 0)
        {
            System.out.print("= ");
            for (String value : list_commands())
            {
                System.out.println(value);
            }
        } else if (cmnd.compareTo("quit") == 0)
        {
            // Confirm we got the quit command then go ahead and close up shop.
            System.out.println("=");
            quit();
        } else if (cmnd.compareTo("boardsize") == 0)
        {
            try
            {
                boardsize(Integer.parseInt(parser.nextToken()));
                System.out.println("= ");
            } catch (Exception e)
            {
                System.out.println("? boardsize not an integer");
            }
        } else if (cmnd.compareTo("clear_board") == 0)
        {
            clear_board();
            // We're Good.
            System.out.println("=");
        } else if (cmnd.compareTo("komi") == 0)
        {
            try
            {
                komi(Float.parseFloat(parser.nextToken()));
                System.out.println("= ");
            } catch (Exception e)
            {
                System.out.println("? komi not a float");
            }
        } else if (cmnd.compareTo("fixed_handicap") == 0)
        {
            try
            {
                fixed_handicap(Integer.parseInt(parser.nextToken()));
                // TODO
                System.out.println("= ");
            } catch (Exception e)
            {
                System.out.println("? handicap not an integer");
            }
        } else if (cmnd.compareTo("place_free_handicap") == 0)
        {
            try
            {
                place_free_handicap(Integer.parseInt(parser.nextToken()));
                // TODO
                System.out.println("= ");
            } catch (Exception e)
            {
                System.out.println("? handicap not an integer");
            }
        } else if (cmnd.compareTo("set_free_handicap") == 0)
        {
            ArrayList<Vertex> vertices = new ArrayList<Vertex>();
            // Grab the list of all the vertices.
            while (parser.countTokens() != 0)
            {
                vertices.add(new Vertex(parser.nextToken()));
            }
            set_free_handicap(vertices);
            // We're Good.
            System.out.println("=");
        } else if (cmnd.compareTo("play") == 0)
        {
            play(Color.parseColor(parser.nextToken()), new Vertex(parser.nextToken()));
            // We're Good.
            System.out.println("=");
        } else if (cmnd.compareTo("genmove") == 0)
        {
            System.out.println("= " + genmove(Color.parseColor(parser.nextToken())));
        } else if (cmnd.compareTo("undo") == 0)
        {
            undo();
            System.out.println("= ");
        } else if (cmnd.compareTo("showboard") == 0)
        {
            System.out.println("=");
            System.out.print(showboard());
        } else
        {
            System.out.println("? unknown command");
        }
        System.out.println();// Put empty new line to indicate end of reply
    }
}
